3.3.1 Async---Await原理

async、await 优缺点: async 和 await 相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码。
缺点在于滥用 await 可能会导致性能问题,
因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。
链接:https://juejin.im/post/5ba34e54e51d450e5162789b

async详解

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

Thunk 函数的定义,它是"传名调用"的一种实现策略,用来替换某个表达式

ES6 Generators 的工作原理

Generator 函数是 ES6 提供的一种异步编程解决方案,
语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,
也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。
返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

function* test() {
  yield 'a';
}

test.next(); // value: "a", done: false}

调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。
每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。   
value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值; 
done属性是一个布尔值,表示是否遍历结束
1
2
3
4
5
6
7
8
9
10
  • 传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做"协程"(coroutine),意思是多个线程互相协作,完成异步任务。
    • Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)

async 函数的实现

async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args){
  // ...
}

// 等同于

function fn(args){ 
  return spawn(function*() {
    // ...
  }); 
}
1
2
3
4
5
6
7
8
9
10
11

所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。

下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    var gen = genF();
    function step(nextF) {
      try {
        var next = nextF();
      } catch(e) {
        return reject(e); 
      }
      if(next.done) {
        return resolve(next.value);
      } 
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });      
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

简介

  • async 函数是什么?一句话,它就是 Generator 函数的语法糖。

    generator 函数需要通过调用next()方法,才能往后执行到下一个yield,但是 async 函数却不需要,它能够自动向后执行

    await 可以理解为是 async wait 的简写。await 必须出现在 async 函数内部,不能单独使用

    await 字面上使得 JavaScript 等待,直到 promise 处理完成,然后将结果继续下去。

    await不能工作在顶级作用域,需要将await代码包裹在一个async函数中

  • demo

前文有一个 Generator 函数,依次读取两个文件。

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
==================================
写成async函数,就是下面这样。

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数对 Generator 函数的改进
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  • 当一个 async 函数中有多个 await命令时,如果不想因为一个出错而导致其与的都无法执行,应将await放在try...catch语句中执行
async function testAwait () {
    try {
        await func1()
        await func2()
        await func3()
    } catch (error) {
        console.log(error)
    }
}
1
2
3
4
5
6
7
8
9
  • 并发执行 await 命令: await 是顺序执行的,Promise.all() 是并行的
// 方法 1
let [res1, res2] = await Promise.all([func1(), func2()])

// 方法 2
let func1Promise = func1()
let func2Promise = func2()
let res1 = await func1Promise
let res2 = await func2Promise


1
2
3
4
5
6
7
8
9
10
  • async方法: 一个class方法同样能够使用async,只需要将async放在它之前就可以

就像这样:

class Waiter {
   async wait () {
       return await Promise.resolve(1)
   }
}
new Waiter().wait().then(alert) // 1

=======================

# Class methods:
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jaffathecake').then(…);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • sleep(): 让线程休眠一段时间
// wait ms milliseconds
function wait(ms) {
  return new Promise(r => setTimeout(r, ms));
}

async function hello() {
  await wait(500);
  return 'world';
}
1
2
3
4
5
6
7
8
9

举例说明啊,你有三个请求需要发生,第三个请求是依赖于第二个请求的解构第二个请求依赖于第一个请求的结果。若用 ES5实现会有3层的回调,若用Promise 实现至少需要3个then。一个是代码横向发展,另一个是纵向发展。今天指给出 async-await 的实现哈~

//我们仍然使用 setTimeout 来模拟异步请求
function sleep(second, param) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(param);
        }, second);
    })
}

async function test() {
    let result1 = await sleep(2000, 'req01');
    let result2 = await sleep(1000, 'req02' + result1);
    let result3 = await sleep(500, 'req03' + result2);
    console.log(`
        ${result3}
        ${result2}
        ${result1}
    `);
}

test();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

egg 中控制器写法

const Controller = require('egg').Controller;

class NewsController extends Controller {
  async list() {
    // const dataList = {
    //   list: [
    //     { id: 1, title: 'this is news 1', url: '/news/1' },
    //     { id: 2, title: 'this is news 2', url: '/news/2' }
    //   ]
    // };
    // await this.ctx.render('news/list.tpl', dataList);
    const ctx = this.ctx;
    const page = ctx.query.page || 1;
    const newsList = await ctx.service.news.list(page);
    await ctx.render('news/list.tpl', { list: newsList });
  }
}

module.exports = NewsController;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

for 循环中使用 async/await

曾经 vue 中在 forEach 中使用 await 会报错,就换成 for of; 不能在常规函数里使用 await 如果我们试图在非 async 函数里使用 await,就会出现一个语法错误:

  • foreach 是同步操作并发操作,可以支持,需要稍微改造下 forEach:
demo:
function test(a) {
  return new Promise(function(resolve,reject){
    if (a){
      resolve(true);
    } else {
      reject(false);
    }
  })
}

async function demo() {
 let data = [{id:1},{id:2}];
 let flag = true
 data.forEach(function(ele,index) {
   if(ele.id == 2) {
     flag = await test(false);  // 这样写相当于 await 不在 async 异步函数内
   }
 })
 
 console.log(flag)
 if(!flag) {
    console.log(666,"信息未填写完")
    return false;
 }
  // 继续下面
}

demo()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  • demo
function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function test(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('hello world', 50);
1
2
3
4
5
6
7
8
9
10
11
12

参考资料

参考